// std::void_t解析
template <typename... Ts>
using void_t = void;

// 这个定义包含两个关键部分:
1.模板参数包
 -typename... Ts 是一个可变参数模板
 -它可以接受任意数量的类型参数

// 2.类型别名
 -using void_t = void;定义了一个别名模板
 -无论传入什么类型参数,它总是别名为void

// 工作原理
std::void_t的魔力在于模板参数的实例化检查: 
1.当编译器处理void_t<T1, T2, ...>时, 它必须: 
-检查每个类型参数T1, T2, ...是否有效
-只有当所有参数都有效时, 才能实例化模板

// 2.关键点: 如果任何一个类型参数无效(如decltype(表达式)中的表达式无效), 则发生SFINAE失败
-模板实例化失败
-但这不是编译错误, 而是让编译器寻找其他可能匹配的模板

// 实际示例解析
// 在HasToString特化中的使用
template <typename T>
struct HasToString<T, std::void_t<decltype(std::declval<T>().toString())>>
    : std::true_type
{

};

编译器尝试实例化std::void_t<decltype(std::declval<T>().toString())>
它必须先检查decltype(std::declval<T>().toString())是否有效
如果T有toString()方法, 这个表达式有效, void_t成功实例化为void
如果T没有toString()方法, 表达式无效, 导致SFINAE失败, 编译器回退到主模板

// 为什么在 decltype 之后还需要 std::void_t
关键在于SFINAE(替换失败不是错误)的触发位置和方式。在模板特化中: 
SFINAE需要在模板参数替换过程中发生, 而不仅仅是在表达式评估中。为什么单独的 decltype 不足以触发SFINAE
如果我们尝试这样写: 
// 错误示例！不会按预期工作
template<typename T>
struct HasToString<T, decltype(std::declval<T>().toString())> 
    : std::true_type {};
这会有几个问题: 
1.类型不匹配: decltype(...)会得到 toString()的返回类型, 这可能是 std::string 或其他类型, 而模板参数期望的是 void
2.直接替换失败: 如果 T 没有 toString()方法, 这会导致硬错误(hard error), 而不是SFINAE

std::void_t 的关键作用
1.类型统一: 无论 toString()返回什么类型, std::void_t 都会将其转换为 void, 确保与默认模板参数类型匹配
2.SFINAE友好的上下文: std::void_t 是专门设计用于SFINAE模式的, 它会在其参数类型无效时触发SFINAE





// 为什么不直接使用void？
// 您可能会想, 为什么不直接写: 
template <typename T>
struct HasToString<T, void> // 这不起作用！
    : std::true_type
{
};
这不起作用, 因为: 
这里的void是一个具体类型, 不会触发SFINAE检查
无论T是什么类型, 这个特化都会匹配
我们需要的是"仅当T有toString()方法时才匹配"的条件

// 总结
std::void_t的巧妙之处在于: 
它利用了C++模板实例化规则
通过要求编译器检查所有模板参数是否有效
最终返回类型永远是void, 但这不重要, 重要的是它的SFINAE行为

